﻿/************************ (C) COPYLEFT 2018 Merafour *************************
* File Name          : mIni.c
* Author             : Merafour
* Last Modified Date : 04/05/2020
* Description        : 配置文件解析.
********************************************************************************
* https://merafour.blog.163.com
* merafour@163.com
* https://github.com/merafour
******************************************************************************/
#include "Ini.h"
#include "mIni.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <assert.h>
//#include <errno.h>
//#include <ctype.h>
#include <stdarg.h>

//enum keylen{ KEYVALLEN = 256};
//#define CFGPATH "./cfg.data"

/***
Ref:https://www.runoob.com/cprogramming/c-function-isspace.html
C 库函数 int isspace(int c) 检查所传的字符是否是空白字符。
标准的空白字符包括：
' '     (0x20)    space (SPC) 空格符
'\t'    (0x09)    horizontal tab (TAB) 水平制表符    
'\n'    (0x0a)    newline (LF) 换行符
'\v'    (0x0b)    vertical tab (VT) 垂直制表符
'\f'    (0x0c)    feed (FF) 换页符
'\r'    (0x0d)    carriage return (CR) 回车符
**/
static int _isspace(const char c)
{
	if(' '==c)  return 1; // (0x20)    space (SPC) 空格符
	if('\t'==c) return 1; // (0x09)    horizontal tab (TAB) 水平制表符    
	if('\n'==c) return 1; // (0x0a)    newline (LF) 换行符
	if('\v'==c) return 1; // (0x0b)    vertical tab (VT) 垂直制表符
	if('\f'==c) return 1; // (0x0c)    feed (FF) 换页符
	if('\r'==c) return 1; // (0x0d)    carriage return (CR) 回车符
	return 0;
}

/*   删除左边的空格   */
static char * l_trim(char * szOutput, const char *szInput)
{
//    assert(szInput != NULL);
//    assert(szOutput != NULL);
//    assert(szOutput != szInput);
    for(; (*szInput != '\0') && (_isspace(*szInput)); ++szInput){
        ;
    }
    return strcpy(szOutput, szInput);
}

/*   删除右边的空格   */
static char *r_trim(char *szOutput, const char *szInput)
{
    char *p = NULL;
//    assert(szInput != NULL);
//    assert(szOutput != NULL);
//    assert(szOutput != szInput);
    strcpy(szOutput, szInput);
    for(p = szOutput + strlen(szOutput) - 1; (p >= szOutput) && (_isspace(*p)); --p){
        ;
    }
    *(++p) = '\0';
    return szOutput;
}

/*   删除两边的空格   */
static char * a_trim(char * szOutput, const char * szInput)
{
    char *p = NULL;
//    assert(szInput != NULL);
//    assert(szOutput != NULL);
    l_trim(szOutput, szInput);
    for(p = szOutput + strlen(szOutput) - 1;(p >= szOutput) && (_isspace(*p)); --p){
        ;
    }
    *(++p) = '\0';
    return szOutput;
}
/*   获取一行   */
/***
 * Ini: Ini空间指针
 * iSize: Ini空间大小
 * ini_pos: Ini内容索引指针
 * iLen: Ini有效数据长度
**/
static char *serach_gets(const char Ini[], const uint16_t iSize, uint16_t* const ini_pos, const uint16_t iLen, char * const str, const int n)
{
    int _end;
    int count=0;
    char _byte=0;
    _end = *ini_pos + n;
    if(n>=iSize) return NULL;
    memset(str, 0, (size_t)n);
    for(count=0; count<n-1; count++)
    {
        if(*ini_pos>=iLen) return  NULL;
        if(*ini_pos>=_end) return  NULL;
        _byte = Ini[(*ini_pos)++];
        str[count] = _byte;
        if('\n'==_byte) break;
        if('\0'==_byte) break;
    }
    //str[count] = '\0';
    str[n-1] = '\0';
    /*if(0==count)
    {
        printf("[%s-%d] stream->pos:%d stream->_stext:%d _end:%d _byte:0x%X\n", __func__, __LINE__, stream->pos, stream->_stext, _end, _byte);
        return  NULL;
    }*/
    return  str;
}
// 跳到下一行
static uint16_t next_gets(const char Ini[], const uint16_t iSize, const uint16_t _seek)
{
    char _byte=0;
    uint16_t _pos;                 // 当前文件指针位置
    if(_seek>=iSize) return iSize-1;
    for(_pos=_seek; _pos<iSize; _pos++)
    {
        _byte = Ini[_pos];
        if('\n'==_byte)
        {
            _pos++;   // skip  '\n'
            break;
        }
        if('\0'==_byte) break;
    }
    return  _pos;
}
// 跳到前一行
static uint16_t previ_gets(const char Ini[], const uint16_t iSize, const uint16_t _seek)
{
    char _byte=0;
    uint16_t _pos=0;                 // 当前文件指针位置
    if(_seek>=iSize) return iSize-1;
    for(_pos=_seek; _pos>0; _pos--)
    {
        _byte = Ini[_pos];
        if('\n'==_byte)
        {
            _pos++;   // skip  '\n'
            break;
        }
        if('\0'==_byte)
        {
            _pos++;   // skip  '\0'
            break;
        }
    }
    _byte = Ini[_pos];
    if((0==_pos) && (('\n'==_byte) || ('\0'==_byte)))
    {
        _pos++;   // skip  '\n' or '\0'
    }
    return  _pos;
}
/*   在当前位置插入一行数据   */
// const int tsize;        // buf 大小
static int _insert(char Ini[], const uint16_t iSize, const int seek, const char item[])
{
    int _size;
    int i;
    if(seek>=iSize) return -1;
    // move item
    _size = (int)strlen(item) + 2; // "\r\n"
    if(_size>=iSize) return -2;
    //printf("[%s-%d] Item[%d]:%s \r\n", __func__, __LINE__, _size, item);
    // 所有数据向后移动 _size byte
    for(i=iSize-1; i>=(seek+_size); i--)
    {
        Ini[i] = Ini[i-_size];
    }
    // insert curr pos
    memcpy(&Ini[seek], item, _size);
    Ini[seek+_size-2] = '\r';
    Ini[seek+_size-1] = '\n';
    //Ini->tlen = (int)strlen(Ini);
    return 0;
}
/*   格式化插入一行数据   */
static int insert(char Ini[], const uint16_t iSize, const int seek, const char *__format, ...)
{
    char buf[INI_ITEM_LEN];
    //int _size;
    //int i;
    va_list ap;
    memset(buf, 0, sizeof(buf));
    va_start(ap, __format);
    //vprintf(__format, ap);
    //snprintf(prt, __n-_size, __format, ap);
    vsnprintf(buf, sizeof(buf), __format, ap);
    va_end(ap);
#if 0
    // move item
    _size = strlen(buf);
    //printf("[%s-%d] Item[%d]:%s", __func__, __LINE__, _size, buf);
    // 所有数据向后移动 _size byte
    for(i=Size()-1; i>=(seek+_size); i--)
    {
        Info[i] = Info[i-_size];
    }
    // insert curr pos
    memcpy(&Info[seek], buf, _size);
    _isize = strlen(Info);
#endif
    _insert(Ini, iSize, seek, buf);
    return 0;
}
/*   获取一段信息,带默认值   */
int mIni_get_field(const char Ini[], const uint16_t iSize, const char section[], const char _key[], const char dft[], char _value[])
{
    int dft_size = (int)strlen(dft);
    //printf("[%s-%d] \n", __func__, __LINE__);
    int ret = mIni_get_section_key_value(Ini, iSize, section, _key, _value);
    if(0!=ret)
    {
        memcpy(_value, dft, dft_size);
        _value[dft_size] = '\0';
    }
    return 0;
}
/*   初始化样例   */
int mIni_list_samples(char Ini[], const uint16_t iSize)
{
    // 清理缓存
    memset(Ini, 0x00, iSize);
    mIni_print(Ini, iSize, "# Ini 配置文件 \r\n");
    mIni_print(Ini, iSize, "# '#' 后面的内容为注释 \r\n");
    mIni_print(Ini, iSize, "# Version:	%s \r\n", INI_FW_VER);
    mIni_print(Ini, iSize, "# [EG1] 表示 EG1 段的开始,到下一个段之前结束 \r\n");
    mIni_print(Ini, iSize, "# Key=Val 表示键值对,且键值对必须在段里面否则不解析 \r\n");
    mIni_print(Ini, iSize, "# 以下为示例\r\n");
    mIni_print(Ini, iSize, "# \r\n");
    mIni_print(Ini, iSize, "# 日志选项 \r\n");
    mIni_print(Ini, iSize, "# no:关闭,yes:开启 \r\n");
    //mIni_print(Ini, "[LOG] \r\n");
    //mIni_print(Ini, "TurnOn=no \r\n");
    //mIni_print(Ini, "[End] \r\n");
    mIni_print(Ini, iSize, "# 文件结束 \r\n");
    //mIni_update(Ini, "Test", "Version", INI_FW_VER);
    mIni_update_item(Ini, iSize, "LOG", "TurnOn", "%s=\"%s\" ", "TurnOn", "no");
    mIni_update_item(Ini, iSize, "AppName", "KeyName", "%s=\"%s\" ", "KeyName", "0x12345678");
    return 0;
}
// 构造一个空 Ini数据结构
int mIni_list_empty(char Ini[], const uint16_t iSize)
{
    // 清理缓存
    memset(Ini, 0x00, iSize);
    mIni_print(Ini, iSize, "#\r\n");
    // Ini结束
    mIni_print(Ini, iSize, "[End]\r\n");
    return 0;
}

/*   获取一个段的地址   */
int mIni_get_section(const char Ini[], const uint16_t iSize, const char AppName[], int* const _pos)
{
    char appname[32];
    char *buf;//,*c;
    char buf_i[INI_ITEM_LEN], buf_o[INI_ITEM_LEN];
    int found=0; /* 1 AppName 2 KeyName */
    uint16_t ini_pos;
    const uint16_t iLen = strlen(Ini);
    memset( appname, 0, sizeof(appname) );
    snprintf( appname, sizeof(appname)-1,"[%s]", AppName );

    ini_pos = 0;
    //printf("[%s-%d] pos:%d size:%d \n", __func__, __LINE__, pos, size);
    while( (ini_pos<iLen/*Ini->tlen*/) && (ini_pos<iSize) && serach_gets(Ini, iSize, &ini_pos, iLen, buf_i, INI_ITEM_LEN)!=NULL )
    {
        // 读取一行
        //printf("[%s-%d] pos:%d size:%d buf_i:%s \n", __func__, __LINE__, pos, size, buf_i);
        l_trim(buf_o, buf_i);
        if( strlen(buf_o) <= 0 )
            continue;
        buf = NULL;
        buf = buf_o;

        //printf("[%s-%d] appname:%s fp.pos:%d found:%d buf:%s\n", __func__, __LINE__, appname, fp.pos, found, buf);
        //if( found == 0 )
        //{
            if( buf[0] != '[' )
            {
                continue;
            }
            else if ( strncmp(buf,appname,strlen(appname))==0 )
            {
                found = 1;
                //printf("\r\n\r\n[%s-%d] text[%d]:\n%s\n", __func__, __LINE__, pos, &text[pos]);
                //continue;
                break;
            }
        //}
        /*else if( found == 1 )
        {
            if('#'==buf[0]) // if( buf[0] == '#' )
            {
                continue;
            }
            //printf("\r\n[%s-%d] text[%d]:\n%s\n", __func__, __LINE__, pos, &text[pos]);
            break;
        }*/
    }
    //printf("[%s-%d] \n", __func__, __LINE__);
    *_pos = ini_pos;//Ini->pos;
    if( found == 1 ) return 0;
    else return -1;
    //return pos;
}
/*   获取一个键值对的地址   */
int mIni_get_section_key(const char Ini[], const uint16_t iSize, const char AppName[], const char KeyName[], int* const _pos)
{
    char appname[32],keyname[32];
    char *buf,*c;
    char buf_i[INI_ITEM_LEN], buf_o[INI_ITEM_LEN];
    int found=0; /* 1 AppName 2 KeyName */
    uint16_t ini_pos;
    const uint16_t iLen = strlen(Ini);
    memset( appname, 0, sizeof(appname) );
    sprintf( appname,"[%s]", AppName );

    ini_pos = 0;
    //printf("[%s-%d] pos:%d size:%d \n", __func__, __LINE__, pos, size);
    while( (ini_pos<iLen/*Ini->tlen*/) && (ini_pos<iSize) && serach_gets(Ini, iSize, &ini_pos, iLen, buf_i, INI_ITEM_LEN)!=NULL )
    {
        // 读取一行
        //printf("[%s-%d] pos:%d size:%d buf_i:%s \n", __func__, __LINE__, pos, size, buf_i);
        l_trim(buf_o, buf_i);
        if( strlen(buf_o) <= 0 )
            continue;
        buf = NULL;
        buf = buf_o;

        //printf("[%s-%d] appname:%s fp.pos:%d found:%d buf:%s\n", __func__, __LINE__, appname, fp.pos, found, buf);
        if( found == 0 )
        {
            if( buf[0] != '[' )
            {
                continue;
            }
            else if ( strncmp(buf,appname,strlen(appname))==0 )
            {
                found = 1;
                continue;
            }
        }
        else if( found == 1 )
        {
            if('#'==buf[0]) // if( buf[0] == '#' )
            {
                continue;
            }
            // add
            //else if(0!=is_empty_line(buf, INI_ITEM_LEN)) continue;
            else if ( buf[0] == '[' ) // 遇到下一个段,结束
            {
                break;
            }
            else
            {
                // 第一个'='前面的字符串为键值对的键
                if( (c = (char*)strchr(buf, '=')) == NULL )
                    continue;
                memset( keyname, 0, sizeof(keyname) );

                //sscanf( buf, "%[^=|^ |^\t]", keyname );
                *c = '\0';
                strcpy(keyname, buf);
                if( strcmp(keyname, KeyName) == 0 )
                {
                    found = 2;
                    break;
                }
                else
                {
                    continue;
                }
            }
        }
    }
    *_pos = ini_pos;//Ini->pos;
    //return pos;
    if( found == 2 ) return 0;
    else return -1;
}
/*   获取一个 Key对应的值   */
int mIni_get_section_key_value(const char Ini[], const uint16_t iSize, const char AppName[], const char KeyName[], char KeyVal[])
{
    int _pos;
    uint16_t ini_pos;
    const uint16_t iLen = strlen(Ini);
    char buf_i[INI_ITEM_LEN], buf_o[INI_ITEM_LEN];
    if(0==mIni_get_section_key(Ini, iSize, AppName, KeyName, &_pos))
    {
        // 往回检索一行必须先回退换行符
        _pos = previ_gets(Ini, iSize, _pos-2); //"\r\n"
        // previ_gets函数不会重置 Ini->pos
        ini_pos/*Ini->pos*/ = _pos;
        //printf("[%s--%d] Key:%s \r\n", __func__, __LINE__, &Ini->text[_pos]);
        serach_gets(Ini, iSize, &ini_pos, iLen, buf_i, INI_ITEM_LEN);
        // 删除两边空格
        a_trim(buf_o, buf_i);
        //printf("[%s--%d] buf_i:%s \r\n", __func__, __LINE__, buf_i);
        //printf("[%s--%d] buf_o:%s \r\n", __func__, __LINE__, buf_o);
        // 拷贝键值对的值
        strcpy(KeyVal, &buf_o[strlen(KeyName)+1]);
        return 0;
    }
    //printf("[%s--%d] \r\n", __func__, __LINE__);
    return -1;
}
/*   更新一个键值对，没有则创建   */
int mIni_update_item(char Ini[], const uint16_t iSize, const char AppName[], const char KeyName[], const char *__format, ...)
{
    int _seek;
    char buf[INI_ITEM_LEN];
    va_list ap;
    memset(buf, 0, sizeof(buf));
    va_start(ap, __format);
    //vprintf(__format, ap);
    //snprintf(prt, __n-_size, __format, ap);
    //vsprintf(buf, __format, ap);
    if(NULL!=KeyName)
    {
        int len;
        len = strlen(KeyName);
        if(len>=INI_ITEM_LEN) // Key长度限制
        {
            va_end(ap);
            return -1;
        }
        memcpy(buf, KeyName, len);
        buf[len] = '=';
        // "\r\n" ==> '\r' '\n' '\0'
        vsnprintf(&buf[len+1], sizeof(buf)-3-len-1, __format, ap);
    }
    else vsnprintf(buf, sizeof(buf)-1, __format, ap);
    va_end(ap);
    //printf("[%s-%d] AppName[%s] KeyName[%s] _seek[%d] _isize[%d] \n", __func__, __LINE__, AppName, KeyName, _seek, Ini->tlen);
    if(0!=mIni_get_section(Ini, iSize, AppName, &_seek))  // 添加新的段
    {
        // "End"为显性结束符
        //if(0!=ini_get_section(Ini, "End", &_seek)) return -2;
        if(0!=mIni_get_section(Ini, iSize, "End", &_seek))
        {
            // 没有 End 就直接在字符串末尾写入
            //return -2;
            //_seek = Ini->tlen;
            _seek = strlen(Ini);
        }
        _seek = previ_gets(Ini, iSize, _seek-3);  // skip curr line
#if 0
        insert(Ini, _seek, "\r\n[%s]\r\n", AppName);  // 在文件末尾插入
        _seek = next_gets(Ini, _seek+3);
        _insert(Ini, _seek, buf);
#else
        insert(Ini, iSize, _seek, "[%s]\r\n%s", AppName, buf);  // 在文件末尾插入
#endif
    }
    else
    {
        if(0==mIni_get_section_key(Ini, iSize, AppName, KeyName, &_seek)) // found
        {
            _seek = previ_gets(Ini, iSize, _seek-3);  // skip curr line
            //printf("[%s-%d] AppName[%s] KeyName[%s] _seek[%d]\n", __func__, __LINE__, AppName, KeyName, _seek);
            mIni_del_item(Ini, iSize, _seek);
            _insert(Ini, iSize, _seek, buf);
        }
        else // new
        {
            if(0!=mIni_get_section(Ini, iSize, AppName, &_seek)) return -2;
            _insert(Ini, iSize, _seek, buf);
        }
    }
    //printf("[%s-%d]\r\n", __func__, __LINE__);
    return 0;
}
/*   删除一个键值对   */
int mIni_del(char Ini[], const uint16_t iSize, const char AppName[], const char KeyName[])
{
    int _seek;
    // 添加新的段
    if(0!=mIni_get_section(Ini, iSize, AppName, &_seek)) return 0;
    if(0==mIni_get_section_key(Ini, iSize, AppName, KeyName, &_seek)) // found
    {
        _seek = previ_gets(Ini, iSize, _seek-3);  // skip curr line
        //printf("[%s-%d] AppName[%s] KeyName[%s] _seek[%d]\n", __func__, __LINE__, AppName, KeyName, _seek);
        mIni_del_item(Ini, iSize, _seek);
    }
    //printf("[%s-%d]\r\n", __func__, __LINE__);
    return 0;
}
/*   删除一行数据   */
int mIni_del_item(char Ini[], const uint16_t iSize, const int seek)
{
    int i;
    int _pos = next_gets(Ini, iSize, seek);
    int _size = _pos - seek;
    // 所有数据向前移动 _size byte
    for(i=seek; i<(iSize-_size); i++)
    {
        Ini[i] = Ini[i+_size];
    }
    for(i=iSize-_size; i<iSize; i++)
    {
        Ini[i] = '\0';
    }
    //printf("[%s-%d] del_item[%d]:\r\n%s", __func__, __LINE__, _size, &Info[seek]);
    return 0;
}
/*   输出一行信息到 Ini   */
int mIni_print(char Ini[], const uint16_t iSize, const char *__format, ...)
{
    char* prt=NULL;
    const size_t __n = iSize;
    size_t _size=0;
    va_list ap;
    _size = strlen(Ini);
    if(_size>=__n) return -1;
    prt = &Ini[_size];
    va_start(ap, __format);
    //vprintf(__format, ap);
    //snprintf(prt, __n-_size, __format, ap);
    vsnprintf(prt, __n-_size, __format, ap);
    va_end(ap);
    //Ini->tlen = (int)strlen(Ini);
    return 0;
}

